/*
 * AbstractScript.java
 *
 * Created on November 3, 2007, 10:06 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */
package filecopier.script;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.LinkedList;
import java.net.URL;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import custom.xml.DOMHelper;
import custom.xml.DOMHelperException;

/**
 *
 * @author david
 */
public abstract class AbstractScript implements Script {

    protected File scriptFile = null;
    protected ScriptPath scriptPath = null;
    protected URL xsdURL = null;
    protected DOMHelper domUtilities = null;
    protected List<ScriptEntry> scriptEntries = null;

    /** Creates a new instance of AbstractScript */
    public AbstractScript(final URL xsdURL, final ScriptPath scriptPath) throws DOMHelperException {
        this.xsdURL = xsdURL;
        this.scriptPath = scriptPath;
        domUtilities = new DOMHelper(xsdURL);
        scriptEntries = new LinkedList<ScriptEntry>();
    }

    public AbstractScript(final URL xsdURL, final ScriptPath scriptPath, final File scriptFile) throws IOException, DOMHelperException {
        if (scriptFile == null) {
            throw new IllegalArgumentException("setScriptFile() was passed a null for the script's File object.");
        }
        this.scriptPath = scriptPath;
        this.scriptFile = getFullScriptPath(scriptFile);
        if (!this.scriptFile.exists()) {
            throw new java.io.FileNotFoundException("\"" + scriptFile.getPath() + "\" does not exist.");
        }
        this.xsdURL = xsdURL;
        init();
    }

    public void init() throws IOException, DOMHelperException {
        try {
            domUtilities = new DOMHelper(xsdURL, scriptFile);
            initScriptEntries();
        } catch (DOMHelperException e) {
            // This file may be a valid script file of an old format type. See if it is before throwing the exception.
            // If it is an old format, convert it to the new format.
            if (!convertOldScript()) {
                throw e;
            }
        }
    }

    public void validate() throws DOMHelperException, IOException {
        try {
            // I have to validate using the method that takes the xml script file. The method then calls parse with the
            // xml file as an argument and returns a new Document object. This is necessary because of a (probable) bug
            // in 1.6. Validation will fail unless you write the xml to a file first. You don't have to do this in 1.5
            domUtilities.validate(scriptFile);
        } catch (DOMHelperException e) {
            // This file may be a valid script file of an old format type. See if it is before throwing the exception.
            // If it is an old format, convert it to the new format.
            if (!convertOldScript()) {
                throw e;
            }
        }
    }

    public String getScriptName() {
        return scriptFile != null ? scriptFile.toString() : null;
    }

    public File getScriptFile() {
        return scriptFile;
    }

    public ScriptEntry getScriptEntry(int idx) {
        ScriptEntry scriptEntry = null;
        if (scriptEntries != null) {
            scriptEntry = scriptEntries.get(idx);
        }
        return scriptEntry;
    }

    public ScriptEntry getScriptEntry(final ScriptEntry.PathType pathType, final String pathStr) {
        ScriptEntry scriptEntry = null;
        if (scriptEntries != null) {
            for (ScriptEntry se : scriptEntries) {
                File path = se.getPath(pathType);
                if (path != null && path.toString().equals(pathStr)) {
                    scriptEntry = se;
                    break;
                }
            }
        }
        return scriptEntry;
    }

    public int getScriptEntryIdx(final ScriptEntry.PathType pathType, final String pathStr) {
        int idx = -1;
        if (scriptEntries != null) {
            int i = 0;
            for (ScriptEntry se : scriptEntries) {
                File path = se.getPath(pathType);
                if (path != null && path.toString().equals(pathStr)) {
                    idx = i;
                    break;
                }
                i++;
            }
        }
        return idx;
    }

    public List<ScriptEntry> getScriptEntries() {
        return scriptEntries;
    }

    public boolean moveScriptEntry(final ScriptEntry scriptEntry, int newIdx) {
        boolean moved = false;
        if (scriptEntries != null) {
            for (int i = 0; i < scriptEntries.size(); i++) {
                ScriptEntry se = scriptEntries.get(i);
                if (se.equals(scriptEntry) && i != newIdx) {
                    if (se.getSavedState() == ScriptEntry.SavedState.Saved) {
                        scriptEntry.setSavedState(ScriptEntry.SavedState.Moved);
                    }
                    scriptEntries.remove(se);
                    scriptEntries.add(Math.min(newIdx, scriptEntries.size() - 1), scriptEntry);
                    moved = true;
                    break;
                }
            }
        }
        return moved;
    }

    public void saveToDisk() throws DOMHelperException {
        if (scriptFile == null) {
            throw new NullPointerException("AbstractScript.saveToDisk() failed because AbstractScript.scriptFile is null.");
        }

        int i = 0;
        for (ScriptEntry se : scriptEntries) {
            ScriptEntry.SavedState savedState = se.getSavedState();
            if (savedState == ScriptEntry.SavedState.Added) {
                insertElement(se, i);
            } else if (savedState == ScriptEntry.SavedState.Removed) {
                removeElement(se);
            } else if (savedState == ScriptEntry.SavedState.Moved) {
                removeElement(se);
                insertElement(se, i);
            }
            i++;
        }
        domUtilities.saveToDisk(scriptFile);

        // If we get here, the save succeeded. Change all scriptEntries with a state of Added to Saved. Remove all script
        // entries with a state of Removed.
        for (i = 0; i < scriptEntries.size(); i++) {
            ScriptEntry se = scriptEntries.get(i);
            ScriptEntry.SavedState savedState = scriptEntries.get(i).getSavedState();
            if (savedState == ScriptEntry.SavedState.Added) {
                se.setSavedState(ScriptEntry.SavedState.Saved);
            } else if (savedState == ScriptEntry.SavedState.Removed) {
                scriptEntries.remove(se);
                i--;
            }
        }
    }

    public void saveToDisk(File newScriptFile) throws IOException, DOMHelperException {
        saveToDisk(newScriptFile, false);
    }

    public void saveToDisk(File newScriptFile, boolean tempScript) throws IOException, DOMHelperException {
        newScriptFile = getFullScriptPath(newScriptFile);
        if (scriptFile == null || !scriptFile.getPath().equals(newScriptFile.getPath())) {
            scriptFile = newScriptFile;
            if (scriptFile.exists()) {
                // This file already exists, so delete it.
                scriptFile.delete();
            }
            createDocument(tempScript);
            // Change all scriptEntries with a state of Saved to a state of Added so that they will be saved to the new file.
            // Remove all scriptEntries with a state of Removed. We know that they are not in the file because the file is new.
            for (int i = 0; i < scriptEntries.size(); i++) {
                ScriptEntry se = scriptEntries.get(i);
                ScriptEntry.SavedState savedState = scriptEntries.get(i).getSavedState();
                if (savedState == ScriptEntry.SavedState.Saved) {
                    se.setSavedState(ScriptEntry.SavedState.Added);
                } else if (savedState == ScriptEntry.SavedState.Removed) {
                    scriptEntries.remove(se);
                    i--;
                }
            }
        }

        saveToDisk();
    }

    public int addScriptEntries(final ScriptEntry[] scriptEntries) {
        int addedCt = 0;
        if (scriptEntries == null) {
            throw new IllegalArgumentException("addScriptEntries() was passed a null for the ScriptEntry array.");
        }
        for (int i = 0; i < scriptEntries.length; i++) {
            addScriptEntry(scriptEntries[i]);
            addedCt++;
        }
        return addedCt;
    }

    public int removeScriptEntries(final ScriptEntry[] scriptEntries) {
        int removedCt = 0;
        if (scriptEntries == null) {
            throw new IllegalArgumentException("addScriptEntries() was passed a null for the ScriptEntry array.");
        }
        for (int i = 0; i < scriptEntries.length; i++) {
            removeScriptEntry(scriptEntries[i]);
            removedCt++;
        }
        return removedCt;
    }

    protected File getFullScriptPath(File scriptFile) {
        if (!scriptFile.getPath().contains(System.getProperty("file.separator"))) {
            String fullScriptPath = scriptPath.getFilePath(scriptFile.getPath());
            if (fullScriptPath != null) {
                scriptFile = new File(fullScriptPath);
            }
        }
        return scriptFile;
    }

    public abstract boolean addScriptEntry(final ScriptEntry scriptEntry);

    public abstract boolean removeScriptEntry(final ScriptEntry scriptEntry);

    public abstract boolean insertScriptEntry(final ScriptEntry scriptEntry, int idx);

    public abstract List<ScriptEntry> getScriptEntries(final ScriptEntry.PathType pathType);

    public abstract List<ScriptEntry> getScriptEntries(final ScriptEntry.SavedState savedState);

    public abstract boolean isTemp();

    public abstract void setTemp(boolean temp);

    protected abstract Document createDocument() throws IOException;

    protected abstract Document createDocument(boolean tempScript) throws IOException;

    protected abstract Element createElement(final ScriptEntry scriptEntry);

    protected abstract Element insertElement(final ScriptEntry scriptEntry, int idx);

    protected abstract boolean removeElement(final ScriptEntry se);

    protected abstract boolean convertOldScript() throws DOMHelperException, IOException;

    protected abstract List<ScriptEntry> initScriptEntries();
}
